﻿using System;
using System.Windows;
using System.Windows.Media;

namespace VisualView.Axis
{
    /// <summary>
    /// 时间轴
    /// </summary>
    public class DateTimeAxis : BaseAxis<DateTime>
    {
        /// <summary>
        /// 时间显示范围
        /// </summary>
        public DateTimeAxisRange Range
        {
            get { return (DateTimeAxisRange)GetValue(RangeProperty); }
            set { SetValue(RangeProperty, value); }
        }
        public static readonly DependencyProperty RangeProperty = DependencyProperty.Register("Range", typeof(DateTimeAxisRange), typeof(DateTimeAxis), new PropertyMetadata(RangeChangedNeedRefresh));

        public override AxisValueLocationConvertParam GetValueLocationParam()
        {
            return new DateTimeAxisValueLocationConvertParam(Range.Min, Range.Max, GetLength(), AxisType);
        }

        public override double GetValueLocation(DateTime value, AxisValueLocationConvertParam param)
        {
            if (param is DateTimeAxisValueLocationConvertParam np)
            {
                var temp = (value - np.Min).TotalMilliseconds / np.Difference.TotalMilliseconds;
                return (np.AxisType == AxisType.X || np.AxisType == AxisType.X2) ? temp * np.Length : np.Length - temp * np.Length;
            }
            else return default;
        }

        public override DateTime GetLocationValue(double location, AxisValueLocationConvertParam param)
        {
            if (param is DateTimeAxisValueLocationConvertParam np)
            {
                var temp = TimeSpan.FromMilliseconds(location * np.Difference.TotalMilliseconds / np.Length);
                return (np.AxisType == AxisType.X || np.AxisType == AxisType.X2) ? np.Min + temp : np.Max - temp;
            }
            else return DateTime.MinValue;
        }

        public override void Refresh()
        {
            if (!this.IsLoaded) return;
            var length = GetLength();
            var range = Range;
            var difference = range.Difference;
            using (var dc = Visual.RenderOpen())
            {
                dc.DrawRectangle(Background, null, new Rect(0, 0, this.ActualWidth, this.ActualHeight));
                DrawingAxisLine(dc);
                if (difference.Days > 366)
                {
                    DrawingItem(dc, length, difference.Days / 365, range.Min.Date.AddDays(1).AddDays(-range.Min.Date.DayOfYear), "Y");
                }
                else if (difference.Days > 30)
                {
                    DrawingItem(dc, length, difference.Days / 30, range.Min.Date.AddDays(1).AddDays(-range.Min.Date.Day), "M");
                }
                else if (difference.Days > 0)
                {
                    DrawingItem(dc, length, difference.Days, range.Min.Date, "D");
                }
                else if (difference.Hours > 0)
                {
                    DrawingItem(dc, length, difference.Hours, range.Min.Date.AddHours(range.Min.Hour), "h");
                }
                else if (difference.Minutes > 0)
                {
                    DrawingItem(dc, length, difference.Minutes, range.Min.Date.AddHours(range.Min.Hour).AddMinutes(range.Min.Minute), "m");
                }
                else if (difference.Seconds > 0)
                {
                    DrawingItem(dc, length, difference.Seconds, range.Min.Date.AddHours(range.Min.Hour).AddMinutes(range.Min.Minute).AddSeconds(range.Min.Second), "s");
                }
                else if (difference.Milliseconds > 0)
                {
                    DrawingItem(dc, length, difference.Milliseconds, range.Min.Date.AddHours(range.Min.Hour).AddMinutes(range.Min.Minute).AddSeconds(range.Min.Second).AddMilliseconds(range.Min.Millisecond), "ms");
                }
            }
        }

        private void DrawingItem(DrawingContext dc, double length, int number, DateTime start, string unit)
        {
            var p = GetValueLocationParam();
            var count = Convert.ToInt32(length / (3 * FontSize));
            var pace = GetPace(number, count);
            if (pace < 1) pace = 1;
            var step = Convert.ToInt32(Math.Ceiling(pace));
            for (int i = 0; i <= number + step; i += step)
            {
                var temp = start;
                string str = string.Empty;
                switch (unit)
                {
                    case "Y":
                        temp = temp.AddYears(i);
                        str = temp.ToString("yyyy");
                        break;
                    case "M":
                        temp = temp.AddMonths(i);
                        str = temp.ToString("MM");
                        break;
                    case "D":
                        temp = temp.AddDays(i);
                        str = temp.ToString("dd");
                        break;
                    case "h":
                        temp = temp.AddHours(i);
                        str = temp.ToString("HH");
                        break;
                    case "m":
                        temp = temp.AddMinutes(i);
                        str = temp.ToString("mm");
                        break;
                    case "s":
                        temp = temp.AddSeconds(i);
                        str = temp.ToString("ss");
                        break;
                    case "ms":
                        temp = temp.AddMilliseconds(i);
                        str = temp.ToString("fff");
                        break;
                    default:
                        break;
                }
                var location = GetValueLocation(temp, p);
                if (location < 0 || location > length) continue;
                var sg = CreateTextGeometry(string.Concat(str, unit), FontSize, out double width, out double height);
                if (AxisType == AxisType.X)
                {
                    dc.DrawLine(LinePen, new Point(location, 0), new Point(location, 5));
                    sg.Transform = new MatrixTransform(1, 0, 0, 1, location - width / 2, 5);
                }
                else if (AxisType == AxisType.X2)
                {
                    dc.DrawLine(LinePen, new Point(location, this.ActualHeight), new Point(location, this.ActualHeight - 5));
                    sg.Transform = new MatrixTransform(1, 0, 0, 1, location - width / 2, this.ActualHeight - height - 5);
                }
                else if (AxisType == AxisType.Y)
                {
                    dc.DrawLine(LinePen, new Point(this.ActualWidth, location), new Point(this.ActualWidth - 5, location));
                    sg.Transform = new MatrixTransform(0, -1, 1, 0, this.ActualWidth - 5 - height, location + width / 2);
                }
                else
                {
                    dc.DrawLine(LinePen, new Point(0, location), new Point(5, location));
                    sg.Transform = new MatrixTransform(0, 1, -1, 0, 5 + height, location - width / 2);
                }
                dc.DrawGeometry(TextPen.Brush, TextPen, sg);
            }
        }

        #region 轴范围移动缩放
        public override void MoveRange(double x, double y)
        {
            double Length = 0;
            double MoveLength = 0;
            switch (AxisType)
            {
                case AxisType.X:
                case AxisType.X2:
                    Length = this.ActualWidth;
                    MoveLength = x;
                    break;
                case AxisType.Y:
                case AxisType.Y2:
                    Length = this.ActualHeight;
                    MoveLength = y;
                    break;
                default:
                    break;
            }
            var range = Range.Difference.TotalMilliseconds;
            var step = TimeSpan.FromMilliseconds(range * MoveLength / Length);
            Range = new DateTimeAxisRange(Range.Min - step, Range.Max - step);
        }

        public override void ZoomRange(Point center, double zoom)
        {
            double CenterLength = 0;
            switch (AxisType)
            {
                case AxisType.X:
                case AxisType.X2:
                    CenterLength = center.X;
                    break;
                case AxisType.Y:
                case AxisType.Y2:
                    CenterLength = center.Y;
                    break;
                default:
                    break;
            }
            var param = GetValueLocationParam();
            var centerAxisValue = GetLocationValue(CenterLength, param);
            var newMax = centerAxisValue.AddMilliseconds((Range.Max - centerAxisValue).TotalMilliseconds * zoom);
            var newMin = centerAxisValue.AddMilliseconds((Range.Min - centerAxisValue).TotalMilliseconds * zoom);
            Range = new DateTimeAxisRange(newMin, newMax);
        }
        #endregion
    }

    public struct DateTimeAxisRange
    {
        public DateTime Min { get; set; }
        public DateTime Max { get; set; }

        public TimeSpan Difference { get { return Max - Min; } }

        public DateTimeAxisRange(DateTime min, DateTime max)
        {
            this.Min = max > min ? min : max;
            this.Max = max > min ? max : min;
        }

        public DateTimeAxisRange(DateTime max) : this(DateTime.Now, max) { }
        public DateTimeAxisRange() : this(DateTime.Now.AddMonths(3)) { }
    }

    public class DateTimeAxisValueLocationConvertParam : AxisValueLocationConvertParam
    {
        public DateTime Min { get; protected set; }
        public DateTime Max { get; protected set; }
        public TimeSpan Difference { get; private set; }
        public DateTimeAxisValueLocationConvertParam(DateTime min, DateTime max, double length, AxisType axisType) : base(length, axisType)
        {
            Min = min;
            Max = max;
            Difference = max - min;
        }
    }
}
